package net.qiqbframework.springboot.autoconfig;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Nonnull;
import net.qiqbframework.commandhandling.*;
import net.qiqbframework.commandhandling.gateway.CommandGateway;
import net.qiqbframework.commandhandling.gateway.DefaultCommandGateway;
import net.qiqbframework.common.transaction.TransactionManager;
import net.qiqbframework.config.Configuration;
import net.qiqbframework.config.Configurer;
import net.qiqbframework.config.ConfigurerModule;

import net.qiqbframework.messaging.intercepting.CorrelationDataInterceptor;
import net.qiqbframework.modelling.command.AggregateHandlingMonitor;
import net.qiqbframework.modelling.command.MultiAggregateHandlingMonitor;
import net.qiqbframework.modelling.recording.AggregateChangesRecorder;
import net.qiqbframework.serialization.ChainingConverter;
import net.qiqbframework.serialization.RevisionResolver;
import net.qiqbframework.serialization.SerialVersionUIDRevisionResolver;
import net.qiqbframework.serialization.Serializer;
import net.qiqbframework.serialization.java.JavaSerializer;
import net.qiqbframework.serialization.json.JacksonSerializer;
import net.qiqbframework.springboot.SerializerProperties;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

import java.util.List;
import java.util.Map;

import static org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors;

/**
 *
 */
@AutoConfiguration
@EnableConfigurationProperties(value = {SerializerProperties.class})
public class QiqbAutoConfiguration implements BeanClassLoaderAware {

    private final ApplicationContext applicationContext;

    private ClassLoader beanClassLoader;

    public QiqbAutoConfiguration(ApplicationContext applicationContext) {

        this.applicationContext = applicationContext;
    }


    @ConditionalOnMissingBean
    @Bean
    public CommandGateway commandGateway(CommandBus commandBus) {
        return DefaultCommandGateway.builder().commandBus(commandBus).build();
    }

    @Bean
    @ConditionalOnMissingBean
    public DuplicateCommandHandlerResolver duplicateCommandHandlerResolver() {
        return LoggingDuplicateCommandHandlerResolver.instance();
    }


    @ConditionalOnMissingBean(value = CommandBus.class)
    @Qualifier("localCommandBus")
    @Bean
    public DefaultCommandBus localCommandBus(Configuration qiqbConfiguration, DuplicateCommandHandlerResolver duplicateCommandHandlerResolver) {
        DefaultCommandBus commandBus = new DefaultCommandBusBuilder(qiqbConfiguration)
                 .transactionManager(qiqbConfiguration.getComponent(TransactionManager.class))
                .duplicateCommandHandlerResolver(duplicateCommandHandlerResolver).spanFactory(qiqbConfiguration.getComponent(CommandBusSpanFactory.class)).messageMonitor(qiqbConfiguration.messageMonitor(CommandBus.class, "commandBus")).build();
        commandBus.registerHandlerInterceptor(new CorrelationDataInterceptor<>(qiqbConfiguration.correlationDataProviders()));
        return commandBus;
    }

    @Bean
    public ConfigurerModule serializerConfigurer(@Qualifier("eventSerializer") Serializer eventSerializer, @Qualifier("aggregateSerializer") Serializer aggregateSerializer, @Qualifier("generalSerializer") Serializer generalSerializer

            , List<AggregateHandlingMonitor> aggregateHandlingMonitors) {
        return new ConfigurerModule() {
            @Override
            public void configureModule(Configurer configurer) {
                configurer.registerComponent(AggregateHandlingMonitor.class, configuration -> {
                    final MultiAggregateHandlingMonitor aggregateHandlingMonitor = new MultiAggregateHandlingMonitor(aggregateHandlingMonitors);
                    aggregateHandlingMonitor.addMessageMonitor(new AggregateChangesRecorder(configuration.authUserProvider(), configuration.aggregateJavers()));
                    return aggregateHandlingMonitor;
                });
                configurer.configureAggregateSerializer(c -> aggregateSerializer)
                        // event
                        .configureEventSerializer(c -> eventSerializer).configureSerializer(c -> generalSerializer);

            }

            @Override
            public int order() {
                return 10;
            }
        };
    }


    @Override
    public void setBeanClassLoader(@Nonnull ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }

    @Bean
    @Qualifier("eventSerializer")
    public Serializer eventSerializer(RevisionResolver revisionResolver, SerializerProperties serializerProperties) {
        return buildSerializer(revisionResolver, serializerProperties.getEvents());
    }

    @Bean
    @Qualifier("aggregateSerializer")
    public Serializer aggregateSerializer(RevisionResolver revisionResolver, SerializerProperties serializerProperties) {
        return buildSerializer(revisionResolver, serializerProperties.getAggregates());
    }

    @Bean
    @Qualifier("generalSerializer")
    public Serializer generalSerializer(RevisionResolver revisionResolver, SerializerProperties serializerProperties) {
        return buildSerializer(revisionResolver, serializerProperties.getGeneral());
    }

    @Bean
    @ConditionalOnMissingBean
    public RevisionResolver revisionResolver() {
        return new SerialVersionUIDRevisionResolver();
    }

    private Serializer buildSerializer(RevisionResolver revisionResolver, SerializerProperties.SerializerType serializerType) {
        switch (serializerType) {
            case JACKSON:
                Map<String, ObjectMapper> objectMapperBeans = beansOfTypeIncludingAncestors(applicationContext, ObjectMapper.class);
                ObjectMapper objectMapper = objectMapperBeans.containsKey("defaultQiqbObjectMapper") ? objectMapperBeans.get("defaultQiqbObjectMapper") : objectMapperBeans.values().stream().findFirst().orElseThrow(() -> new NoSuchBeanDefinitionException(ObjectMapper.class));
                ChainingConverter converter = new ChainingConverter(beanClassLoader);
                return JacksonSerializer.builder().revisionResolver(revisionResolver).converter(converter).objectMapper(objectMapper).build();
            case JDK:
            case DEFAULT:
            default:
                return JavaSerializer.builder().revisionResolver(revisionResolver).build();
        }
    }

}
